//------------------------------------------------------------------------------
//	file cd-tool.cpp
//	brief
//		CD management and raw audio ripper tool
//
//	$Id: cd-tool.cpp 245 2014-02-10 01:43:18Z ac.verbeck@gmail.com $
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
//	Copyright and Disclaimer Notice
//
//	Copyright (c) 2013-2014 A.C. Verbeck
//	original author: Michel Helms (MichelHelms@Web.de).
//
//	This file is part of cd-tool: CD management and raw audio ripper tool
//
//	cd-tool is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	cd-tool is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with cd-tool.	 If not, see <http://www.gnu.org/licenses/>.
//
//------------------------------------------------------------------------------
#include <ctype.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include <stdint.h>																//	Standard int types
#include <stdbool.h>															//	Bool

#include <popt.h>
#include <sndfile.h>

#include "CAudioCD.h"
#include "debug.h"

#include "bi.h"

//------------------------------------------------------------------------------
// Local defines
//------------------------------------------------------------------------------
#define OPT_OPERATION		0x0001
#define OPT_DRIVE			0x0002
#define OPT_OUTPUT			0x0004
#define OPT_TRACK			0x0008
#define OPT_ABOUT			0x0100
#define OPT_VERBOSE			0x0200

//------------------------------------------------------------------------------
// Local data
//------------------------------------------------------------------------------
static const char*		operation;					//	operation to be run (used in popt)
static const char*		drive		= "D";			//	CD drive: Input (used in popt)
static const char*		file_name	= "out";		//	Filename: Output (used in popt)
static int				track		= 1;			//	track to rip (used in popt)
static char				file_buff[255];
static poptContext		optCon;

typedef struct {
	const char*		op;
	const char*		file_name;
	const char*		drive;
	int				track;
	int				r_value;
} OPERATOR_PARMS;

typedef void (*OPERATOR_FN)(OPERATOR_PARMS* fm);

typedef struct {
	const char*		op;
	OPERATOR_FN		fn;
} OPERATOR_TAB;

static void op_track_ct(OPERATOR_PARMS* fm);
static void op_toc(OPERATOR_PARMS* fm);
static void op_rip_track(OPERATOR_PARMS* fm);
static void op_rip_all(OPERATOR_PARMS* fm);
static void op_load(OPERATOR_PARMS* fm);
static void op_eject(OPERATOR_PARMS* fm);
static void op_wait(OPERATOR_PARMS* fm);
static void op_lock(OPERATOR_PARMS* fm);
static void op_unlock(OPERATOR_PARMS* fm);
static void op_void(OPERATOR_PARMS* fm);

static OPERATOR_TAB otab[] = {
	{	"track-ct",		op_track_ct		},									//	Track count				0
	{	"toc",			op_toc			},									//	Table of Contents		1
	{	"rip-track",	op_rip_track	},									//	Rip track 				2
	{	"rip-all",		op_rip_all		},									//	Rip All tracks			3
	{	"load",			op_load			},									//	Load CD drive			4
	{	"eject",		op_eject		},									//	Eject CD drive			5
	{	"wait",			op_wait			},									//	Wait for CD drive		6
	{	"lock",			op_lock			},									//	Lock CD drive			7
	{	"unlock",		op_unlock		},									//	Unlock CD drive			8
	{	"voytek",		op_void			}									//	Internal test operator	9
};
#define	OTAB_CT		10


//------------------------------------------------------------------------------
// Main function
//------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
	uint32_t	opt=0;
	int			idx=0;
	bool		hit=false;

	OPERATOR_PARMS p1;

	struct poptOption optionsTable[] = {
		{ "op",		'p', POPT_ARG_STRING, &operation,	'p', "operation: see man page for details",		NULL	},
		{ "drive",	'd', POPT_ARG_STRING, &drive,	 	'd', "CD drive (default: D:)",					NULL	},
		{ "output",	'o', POPT_ARG_STRING, &file_name,	'o', "output filename root (default = out)",	NULL	},
		{ "track",	't', POPT_ARG_INT,   &track,		't', "track number to rip (default = 1)",		NULL	},
		{ "about",	'a', POPT_ARG_NONE,	  NULL,			'a', "display information about the app",		NULL	},
		{ "verbose",'v', POPT_ARG_NONE,	  NULL,			'v', "enable verbose mode",						NULL	},
		POPT_AUTOHELP
		POPT_TABLEEND
	};

//------------------------------------------------------------------------------
//	Process arguments
//------------------------------------------------------------------------------
	optCon = poptGetContext(NULL, argc, (const char **)argv, optionsTable, 0);
	poptSetOtherOptionHelp(optCon, "[OPTIONS]*");
	if (argc == 1) {
		poptPrintHelp(optCon, stdout, 0);
		exit(1);
	}

	while ((idx = poptGetNextOpt(optCon)) >= 0) {
		switch (idx) {
		case 'p':
			opt |= OPT_OPERATION;
			break;
		case 'd':
			opt |= OPT_DRIVE;
			break;
		case 'o':
			opt |= OPT_OUTPUT;
			break;
		case 't':
			opt |= OPT_TRACK;
			break;
		case 'a':
			opt |= OPT_ABOUT;
			break;
		case 'v':
			opt |= OPT_VERBOSE;
			break;
		}
	}
	if (idx < -1) {
		fprintf(stderr, "%s: %s\n",
		        poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
		        poptStrerror(idx));
		exit(opt);
	}
	if (opt & OPT_ABOUT) {
		bldinfoPrintBldInfo();
		exit(0);
	}
	if ((opt & OPT_OPERATION) == 0) {
		printf("Operation (op) required!\n");
		exit(0);
	}

//------------------------------------------------------------------------------
//	Execute the operation (if we can find it)
//------------------------------------------------------------------------------
	p1.op 			= operation;
	p1.file_name	= file_name;
	p1.drive		= drive;
	p1.track		= track;
	p1.r_value		= 0;

//------------------------------------------------------------------------------
//	Execute the operation (if we can find it)
//------------------------------------------------------------------------------
	for (char *p = (char*)operation; *p; ++p) *p = tolower(*p);					//	Convert the string to lower case
	for (idx=0; idx<OTAB_CT; idx++) {											//	Go through the OTAB table
		if (strcmp(operation, otab[idx].op) == 0) {								//	If we have a match...
			(otab[idx].fn)(&p1);												//	Call the function
			hit = true;															//	Indicate that we executed the function
		}
	}

//------------------------------------------------------------------------------
//	If no function was executed, then note the fact and exit
//	If a function was executed, return the value from the function
//------------------------------------------------------------------------------
	if (hit == false)
		printf("Did not find operation: %s\n",operation);

	return (hit == true) ? p1.r_value : 0;
}


//------------------------------------------------------------------------------
//	Function:
//		op_toc()
//
//	Description:
//		Extracts / prints the track count
//		Extracts / prints the table of contents from a CD
//
//	Parameters:
//		Single parameter: pointer to OPERATOR_PARMS
//
//	Returns:
//		Nothing (void)
//
//	Notes:
//		Returns the track count to the system via the BASH $? variable,
//		or the  %errorlevel% variable in windows command shell.
//
//------------------------------------------------------------------------------
static void op_toc(OPERATOR_PARMS* fm)
{
	CAudioCD AudioCD;
	char drive = fm->drive[0];

	if (AudioCD.Open(drive) == false) {
		ErrorExit("AudioCD.Open()");
		fm->r_value = 1;
		return;
	}

//------------------------------------------------------------------------------
//	Get / Print the Track Count
//------------------------------------------------------------------------------
	ULONG TrackCount = AudioCD.GetTrackCount();
	printf("track count: %lu\n", TrackCount);

//------------------------------------------------------------------------------
//	Get / Print Track time for each track
//------------------------------------------------------------------------------
	for (ULONG i=0; i<TrackCount; i++) {
		ULONG Time = AudioCD.GetTrackTime(i);
		double tr_sz = (double)AudioCD.GetTrackSize(i)/1048576.0;
		printf(" track %02lu: %lu:%.2lu: %.2fmb\n", i+1, Time/60, Time%60, tr_sz);
	}
	fm->r_value = (int)TrackCount;

	AudioCD.Close();
}


//------------------------------------------------------------------------------
//	Function:
//		op_track_ct()
//
//	Description:
//		Extracts / prints the track count
//
//	Parameters:
//		Single parameter: pointer to OPERATOR_PARMS
//
//	Returns:
//		Nothing (void)
//
//	Notes:
//		Returns the track count to the system via the BASH $? variable,
//		or the  %errorlevel% variable in windows command shell.
//
//------------------------------------------------------------------------------
static void op_track_ct(OPERATOR_PARMS* fm)
{
	CAudioCD AudioCD;
	char drive = fm->drive[0];

	if (AudioCD.Open(drive) == false) {
		ErrorExit("AudioCD.Open()");
		fm->r_value = 1;
		return;
	}

//------------------------------------------------------------------------------
//	Get / Print the Track Count
//------------------------------------------------------------------------------
	fm->r_value = (int)AudioCD.GetTrackCount();
	printf("track count: %d\n", fm->r_value);

	AudioCD.Close();
}


//------------------------------------------------------------------------------
//	Function:
//		op_rip_track()
//
//	Description:
//		Rips a single track from the CD
//
//	Parameters:
//		Single parameter: pointer to OPERATOR_PARMS
//
//	Returns:
//		Nothing (void)
//
//	Notes:
//		Tracks are "one based", thus they start with track 1.
//		The device driver sees the tracks as starting with 0.
//		To match them, the ripped track must decremented by one.
//------------------------------------------------------------------------------
static void op_rip_track(OPERATOR_PARMS* fm)
{
	CAudioCD AudioCD;
	char drive = fm->drive[0];													//	drive is the first character of the array
	int  track = fm->track-1;													//	select the track (starts at 1)
	sprintf(file_buff, "%s.wav", fm->file_name);								//	build the file name

	track = (track < 0) ? 0 : track;											//	keep the track in range

	if (AudioCD.Open(drive) == false) {											//	open the device
		ErrorExit("AudioCD.Open()");
		fm->r_value = 1;
		return;
	}

//------------------------------------------------------------------------------
//	Extract the track
//------------------------------------------------------------------------------
	if (AudioCD.ExtractTrack(track, file_buff) == false) {						//	read the track
		ErrorExit("AudioCD.ExtractTrack");
		fm->r_value = 1;
		return;
	}

	AudioCD.Close();															//	close the device
}


//------------------------------------------------------------------------------
//	Function:
//		op_rip_all()
//
//	Description:
//		Rip all tracks from the CD
//
//	Parameters:
//		Single parameter: pointer to OPERATOR_PARMS
//
//	Returns:
//		Nothing (void)
//
//	Notes:
//
//------------------------------------------------------------------------------
static void op_rip_all(OPERATOR_PARMS* fm)
{
	CAudioCD AudioCD;
	char drive = fm->drive[0];													//	drive is the first character of the array
	int  track = 0;																//	start at first track
	int	track_ct = 0;															//	max track

	if (AudioCD.Open(drive) == false) {											//	open the device
		ErrorExit("AudioCD.Open()");
		fm->r_value = 1;
		return;
	}
	track_ct = (int)AudioCD.GetTrackCount();									//	get max track

	printf("Starting rip...\n");												//	display start up
	for (track = 0; track < track_ct; track++) {								//	loop through all the tracks
		sprintf(file_buff, "%s-%02d.wav", fm->file_name, track+1);				//	build the file name
		printf("%s\r",file_buff);												//	display file being ripped
		if (AudioCD.ExtractTrack(track, file_buff) == false) {					//	extract the track
			ErrorExit("AudioCD.ExtractTrack");
			fm->r_value = 1;
			return;
		}
	}
	AudioCD.Close();															//	close the device
	printf("\nFinish rip...\n");												//	display
}


//------------------------------------------------------------------------------
//	Function:
//		op_load()
//
//	Description:
//		Loads the CD
//
//	Parameters:
//		Single parameter: pointer to OPERATOR_PARMS
//
//	Returns:
//		Nothing (void)
//
//	Notes:
//
//------------------------------------------------------------------------------
static void op_load(OPERATOR_PARMS* fm)
{
	CAudioCD AudioCD;
	char drive = fm->drive[0];

	if (AudioCD.LoadCD(drive) == false) {
		ErrorExit("AudioCD.LoadCD()");
		fm->r_value = 1;
		return;
	}
	AudioCD.Close();
}


//------------------------------------------------------------------------------
//	Function:
//		op_eject()
//
//	Description:
//		Ejects the CD drive
//
//	Parameters:
//		Single parameter: pointer to OPERATOR_PARMS
//
//	Returns:
//		Nothing (void)
//
//	Notes:
//
//------------------------------------------------------------------------------
static void op_eject(OPERATOR_PARMS* fm)
{
	CAudioCD AudioCD;
	char drive = fm->drive[0];

	if (AudioCD.Open(drive) == false) {
		ErrorExit("AudioCD.Open()");
		fm->r_value = 1;
		return;
	}
	if (AudioCD.UnlockCD() == false) {
		ErrorExit("AudioCD.UnlockCD()");
		fm->r_value = 1;
		return;
	}
	if (AudioCD.EjectCD() == false) {
		ErrorExit("AudioCD.EjectCD()");
		fm->r_value = 1;
		return;
	}
	AudioCD.Close();
}


//------------------------------------------------------------------------------
//	Function:
//		op_wait()
//
//	Description:
//		Waits for a CD to be loaded
//
//	Parameters:
//		Single parameter: pointer to OPERATOR_PARMS
//
//	Returns:
//		Nothing (void)
//
//	Notes:
//		This function waits until the CD is loaded.
//		Cannot be called unless the drive has been "loaded" or "opened"
//
//------------------------------------------------------------------------------
static void op_wait(OPERATOR_PARMS* fm)
{
	CAudioCD AudioCD;
	char drive = fm->drive[0];

	if (AudioCD.IsCDReady(drive) == false) {
		ErrorExit("AudioCD.IsCDReady()");
		fm->r_value = 1;
		return;
	}
}


//------------------------------------------------------------------------------
//	Function:
//		op_lock()
//
//	Description:
//		Locks the CD so it cannot be ejected.
//
//	Parameters:
//		Single parameter: pointer to OPERATOR_PARMS
//
//	Returns:
//		Nothing (void)
//
//	Notes:
//
//------------------------------------------------------------------------------
static void op_lock(OPERATOR_PARMS* fm)
{
	CAudioCD AudioCD;
	char drive = fm->drive[0];

	if (AudioCD.Open(drive) == false) {
		ErrorExit("AudioCD.Open()");
		fm->r_value = 1;
		return;
	}
	AudioCD.LockCD();
}


//------------------------------------------------------------------------------
//	Function:
//		op_unlock()
//
//	Description:
//		Unlocks the CD drive
//
//	Parameters:
//		Single parameter: pointer to OPERATOR_PARMS
//
//	Returns:
//		Nothing (void)
//
//	Notes:
//
//------------------------------------------------------------------------------
static void op_unlock(OPERATOR_PARMS* fm)
{
	CAudioCD AudioCD;
	char drive = fm->drive[0];

	if (AudioCD.Open(drive) == false) {
		ErrorExit("AudioCD.Open()");
		fm->r_value = 1;
		return;
	}
	AudioCD.UnlockCD();
}


//------------------------------------------------------------------------------
//	Function:
//		op_void()
//
//	Description:
//		Internal function that displays the incoming OPERATOR_PARMS
//		structure.
//
//	Parameters:
//		Single parameter: pointer to OPERATOR_PARMS
//
//	Returns:
//		Nothing (void)
//
//	Notes:
//		Returns the incoming structure track variable
//		to the system via the BASH $? variable.
//		Or the  %errorlevel% variable in windows command shell.
//
//		This function uses the mnemonic "voytek".  This is a character from
//		the William Gibson novel "Pattern Recognition".
//
//------------------------------------------------------------------------------
static void op_void(OPERATOR_PARMS* fm)
{
	printf("hit: void\n");
	printf("     operation: %s\n", fm->op);
	printf("      filename: %s\n", fm->file_name);
	printf("         drive: %s\n", fm->drive);
	printf("         track: %d\n", fm->track);
	printf("  return value: %d\n", fm-> r_value);
	fm->r_value = track;
}

//
//	End: cd-tool.cpp
//
